;=======
;常量
;=======
app_lba_start equ 100

SECTION mbr align=16 vstart=0x7c00
    ;设置段地址
    mov ax,0
    mov ss,ax
    mov sp,ax

    mov ax,[cs:phy_base] ; 程序加载处的物理地址
    mov dx,[cs:phy_base+0x02] ; 4字节
    mov bx,16  
    div bx         ; 取段地址
    mov ds,ax
    mov es,ax

    ;先读取程序头部
    xor di,di
    mov si,app_lba_start
    xor bx,bx
    call read_hard_disk_0

    ;获取程序总长度，并继续读取，直到所有扇区全部load进内存
    mov dx,[0x02]
    mov ax,[0x00]
    mov bx,512
    div bx
    cmp dx,0
    jnz @1 ; 余数不为 0，要读取 ax+1 个扇区。由于已经读了一个扇区了，所以还要读 ax 个。
    dec ax ; 余数为 0，证明刚好 ax 个扇区，不用读取第 ax+1 个扇区了。由于已经读了一个扇区了，所以还要读 ax-1 个。
@1:
    cmp ax,0
    jz direct ; 商为 0，证明总长度不足 512 字节，刚才已经读了一个字节出来了，已经是所有内容了，所以不用再读了

    push ds   ; 会改变 ds 的值，但后面还要以初始的 ds 值为准去读内存，所以先压栈
    mov cx,ax
read_continue:
    inc si
    xor di,di
    mov ax,ds
    add ax,0x20 ; 512个字节就是512个内存单元，就是0x200，所以段地址一次加0x20
    mov ds,ax
    xor bx,bx
    call read_hard_disk_0
    loop read_continue

    pop ds

; 全部读入内存后，进行用户程序的重定位
direct:
    mov dx,[0x08]
    mov ax,[0x06]
    call calc_segment_base
    mov [0x06],ax
    ; 处理程序段
    mov cx,[0x0a]
    mov bx,0x0c  ;表首地址（第一条）
realloc:
    mov ax,[bx]
    mov dx,[bx+2]
    call calc_segment_base
    mov [bx],ax
    add bx,0x04
    loop realloc

    jmp far [0x04]

;================
; 读1个扇区的硬盘数据到内存
; di、si - 扇区 LBA 值，使用di的低12位和si的全部16位，共计28位
; ds:bx - 写入内存逻辑地址
;================
read_hard_disk_0:
    push ax
    push bx
    push cx
    push dx

    ; 0x1f2用于设置读取扇区数
    ; 设置读取一个扇区
    mov dx,0x1f2
    mov al,1
    out dx,al

    ; 0x1f3~0x1f6 用来设置 LBA28，表示硬盘的逻辑扇区
    inc dx
    mov ax,si
    out dx,al

    inc dx
    mov al,ah
    out dx,al
    
    inc dx
    mov ax,di
    out dx,al

    inc dx
    mov al,1100b ; 0x1f6 高四位，表示LBA模式和主/从硬盘
    out dx,al
    
    ; 发送读请求
    inc dx
    mov al,0x20 ; 0x20 表示读
    out dx,al

    ; 0x1f7即是命令端口，也是状态端口
    .waits:
        in ax,dx
        and ax,0x88
        cmp ax,0x08
        jnz .waits

    ; 0x1f0 是 16 位的数据端口
    mov cx,256 ; 共读取 256 字，即 512 byte
    mov dx,0x1f0
    .readw:
        in ax,dx
        mov [bx],ax
        add bx,2
        loop .readw
    
    pop dx
    pop cx
    pop bx
    pop ax

    ret

;====================
; 重定位用户程序
; dx:ax - 用户程序原本的段起始地址
;====================
calc_segment_base:
    push dx
    add ax,[cs:phy_base]
    adc dx,[cs:phy_base+0x02]
    shr ax,4  ;低16位右移4位，空出最左边4位
    ror dx,4  ;高16位循环右移4位，将最低4位的有效值挪到最高4位上
    and dx,0xf000 ;确保只保留最高四位
    or ax,dx  ; 合并
    pop dx
    ret

phy_base: dd 0x10000   ;程序被加载处的物理地址

times 510-($-$$) db 0
db 0x55, 0xaa